package com.placement.refresh.util;

import java.lang.reflect.Method;
import java.util.Comparator;
import java.util.Date;
import java.util.Map;

/**
 * A class used to sort a List of objects, that are from the same class,
 * by a variety of attributes.  The class must contain the appropriate "getter"
 * method for each desired sort attribute.
 * <br><br>
 * Jason Merecki Update: the classes to sort can be of type Map, and 
 * this class will use the "get" method to retrieve values for sorting
 * <br><br>
 *
 * Example Usage:
 * 
 * Note: There are 4 constructors for the ObjectComparator.
 * 
 * // Must be a List object (not just a collection)! List objects include ArrayList and Vectors
 * SOME_LIST_CONTAINING_OBJECTS = new OBJECT_IMPLEMENTING_LIST();   
 * SOME_LIST_CONTAINING_OBJECTS.addAll( SOME_OBJECTS );
 * String[] sort = {"cusip", "issuer", "price"};
 * int[] sortOrder = {ObjectComparator.SORT_DESCENDING, ObjectComparator.SORT_DESCENDING, ObjectComparator.SORT_DESCENDING};
 * // See Collections.sort for details; note that sort is destructive, the List object passed will itself be sorted!
 * // Also make sure that the objects IN the List are of type THE_OBJECT_CLASS_NAME, which is passed to ObjectComparator
 * Collections.sort( SOME_LIST_CONTAINING_OBJECTS, new ObjectComparator("THE_OBJECT_CLASS_NAME", sort, sortOrder) );
 * 
 * At this point, the passed in list of objects will be sorted
 * <br>
 * @author John Cimino
 * @version 1.0
 * @update: Updated the String upper case before calling the string comparison. This will sort the 
 * Strings in case insensitive manner. 
 */
public class ObjectComparator implements Comparator
{
	
	/** Constant to specify a descending sort for the attribute */
	public static final int SORT_DESCENDING = 0;

	/** Constant to specify an ascending sort for the attribute */
	public static final int SORT_ASCENDING = 1;

	/** The fully-qualified class name of the object to be sorted */
	private String sortObjectClassName;

	/** The actual Class object of the object to be sorted */
	private Class sortObjectClass;

	/** An array of Method objects that correspond to each sort attribute */
	private Method sortFieldGetterMethods[];

	/** An array of Strings that represent the argument(s) to pass to the getter method (really only necessary for Map objects) */
	private String sortFieldMethodArgs[];
	
	/** An integer array that corresponds to each sort attribute's sort order */
	private int sortFieldOrder[];

	/** If the classes to sort are instances of map, this is set to true */
	private boolean isMap;
	
	/**
	 * Private Constructor - not used
	 * @exception ObjectComparatorException
	**/
	private ObjectComparator() throws ObjectComparatorException
	{
	}

	/**
	 * Constructor for ObjectComparator when passing in the actual object that will be sorted 
	 * by later on.  This is an alternative to passing in the fully-qualified class name for the
	 * objects that will be sorted.
	 *
	 * @param sortObject An instance of the object that will be sorted on
	 * @param sortFields An array of all the field names to sort by
	 * @param sortFieldOrder An integer array representing the sort order for the fields that will be sorted.
	 *                        Should either contain ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 *
	 * @exception ObjectComparatorException if any errors occurred during creation
	 **/
	public ObjectComparator(
		Object sortObject,
		String[] sortFields,
		int[] sortFieldOrder)
		throws ObjectComparatorException
	{
		this.sortObjectClass = sortObject.getClass();
		this.sortObjectClassName = sortObjectClass.getName();
		this.checkIsMap();
		loadMethods(sortFields, sortFieldOrder);
	}

	/**
	 * Constructor for ObjectComparator when passing in the fully-qualified class name of the objects that will be sorted 
	 * by later on.  This is an alternative to passing in an instance of the actual object.
	 *
	 * @param sortObjectClassName The fully-qualified class name for the objects that will be sorted
	 * @param sortFields An array of all the field names to sort by
	 * @param sortFieldOrder An integer array representing the sort order for the fields that will be sorted.
	 *                        Should either contain ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 *
	 * @exception ObjectComparatorException if any errors occurred during creation
	 **/
	public ObjectComparator(
		String sortObjectClassName,
		String[] sortFields,
		int[] sortFieldOrder)
		throws ObjectComparatorException
	{
		try
		{
			this.sortObjectClass = Class.forName(sortObjectClassName);
			this.sortObjectClassName = sortObjectClassName;
			this.checkIsMap();
		}
		catch (ClassNotFoundException e)
		{
			throw new ObjectComparatorException(
				"Error creating ObjectComparator: " + e);
		}
		loadMethods(sortFields, sortFieldOrder);
	}

	/**
	 * Constructor for ObjectComparator when passing in the actual object that will be sorted 
	 * by later on.  This is an alternative to passing in the fully-qualified class name for the
	 * objects that will be sorted. This constructor is also used when you want to sort by a maximum
	 * of only 3 fields, as opposed to passing in an array of fields to sort by.
	 *
	 * @param sortObject An instance of the object that will be sorted on 
	 * @param sortField1 First field to sort by
	 * @param sortField1SortOrder Sort order for first field, either ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 * @param sortField2 Second field to sort by
	 * @param sortField2SortOrder Sort order for second field, either ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 * @param sortField3 Third field to sort by
	 * @param sortField3SortOrder Sort order for third field, either ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 *
	 * @exception ObjectComparatorException if any errors occurred during creation
	 **/
	public ObjectComparator(
		Object sortObject,
		String sortField1,
		int sortField1SortOrder,
		String sortField2,
		int sortField2SortOrder,
		String sortField3,
		int sortField3SortOrder)
		throws ObjectComparatorException
	{
		this.sortObjectClass = sortObject.getClass();
		this.sortObjectClassName = sortObjectClass.getName();
		this.checkIsMap();

		loadMethods(
			sortField1,
			sortField1SortOrder,
			sortField2,
			sortField2SortOrder,
			sortField3,
			sortField3SortOrder);
	}

	/**
	 * Constructor for ObjectComparator when passing in the fully-qualified class name of the objects that will be sorted 
	 * by later on.  This is an alternative to passing in an instance of the actual object.
	 * This constructor is also used when you want to sort by a maximum
	 * of only 3 fields, as opposed to passing in an array of fields to sort by.
	 *
	 * @param sortObjectClassName An instance of the object that will be sorted on 
	 * @param sortField1 First field to sort by
	 * @param sortField1SortOrder Sort order for first field, either ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 * @param sortField2 Second field to sort by
	 * @param sortField2SortOrder Sort order for second field, either ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 * @param sortField3 Third field to sort by
	 * @param sortField3SortOrder Sort order for third field, either ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 *
	 * @exception ObjectComparatorException if any errors occurred during creation
	 **/
	public ObjectComparator(
		String sortObjectClassName,
		String sortField1,
		int sortField1SortOrder,
		String sortField2,
		int sortField2SortOrder,
		String sortField3,
		int sortField3SortOrder)
		throws ObjectComparatorException
	{

		try
		{
			this.sortObjectClass = Class.forName(sortObjectClassName);
			this.sortObjectClassName = sortObjectClassName;
		}
		catch (ClassNotFoundException e)
		{
			throw new ObjectComparatorException(
				"Error creating ObjectComparator: " + e);
		}
		loadMethods(
			sortField1,
			sortField1SortOrder,
			sortField2,
			sortField2SortOrder,
			sortField3,
			sortField3SortOrder);
	}

	/**
	 * Loads the proper getter method for the field
	 *
	 * @param sortFields An array of all the field names to sort by
	 * @param sortFieldOrder An integer array representing the sort order for the fields that will be sorted.
	 *                        Should either contain ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 *
	 * @exception ObjectComparatorException if any errors occurred during creation
	 **/
	private void loadMethods(String[] sortFields, int[] sortFieldOrder)
		throws ObjectComparatorException
	{
		if (sortFields.length != sortFieldOrder.length)
		{
			throw new ObjectComparatorException(
				"Error creating ObjectComparator. Sort Fields array count "
					+ "doesn't match Sort Field Order array count.");
		}

		//Determine the appropriate Method object for the field to be sorted by
		String sortField = "";
		int sortOrder = 0;
		int length = sortFields.length;
		this.sortFieldGetterMethods = new Method[length];
		this.sortFieldOrder = new int[length];
		this.sortFieldMethodArgs = new String[length];
		int getterCount = 0;
		for (int i = 0; i < length; i++)
		{
			sortField = sortFields[i];
			sortOrder = sortFieldOrder[i];
			if ((sortField != null && sortField != ""))
			{
				this.sortFieldGetterMethods[getterCount] =
					getMethodName(sortField);
				if (this.isMap) this.sortFieldMethodArgs[getterCount]=sortField; // Map objects use method "get" and pass the field name as an arg
				this.sortFieldOrder[getterCount] = sortOrder;
				getterCount++;
			}
		}
	}

	/**
	 * Loads the proper getter method for the field
	 *
	 * @param sortField1 First field to sort by
	 * @param sortField1SortOrder Sort order for first field, either ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 * @param sortField2 Second field to sort by
	 * @param sortField2SortOrder Sort order for second field, either ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 * @param sortField3 Third field to sort by
	 * @param sortField3SortOrder Sort order for third field, either ObjectComparator.SORT_DESCENDING or ObjectComparator.SORT_ASCENDING
	 *
	 * @exception ObjectComparatorException if any errors occurred during creation
	 **/
	private void loadMethods(
		String sortField1,
		int sortField1SortOrder,
		String sortField2,
		int sortField2SortOrder,
		String sortField3,
		int sortField3SortOrder)
		throws ObjectComparatorException
	{
		this.sortFieldGetterMethods = new Method[3];
		this.sortFieldOrder = new int[3];

		if (sortField1 != null && !sortField1.equals(""))
		{
			this.sortFieldGetterMethods[0] = getMethodName(sortField1);
			if (this.isMap) this.sortFieldMethodArgs[0]=sortField1; // Map objects use method "get" and pass the field name as an arg
			this.sortFieldOrder[0] = sortField1SortOrder;
		}
		if (sortField2 != null && !sortField2.equals(""))
		{
			this.sortFieldGetterMethods[1] = getMethodName(sortField2);
			if (this.isMap) this.sortFieldMethodArgs[1]=sortField2; // Map objects use method "get" and pass the field name as an arg
			this.sortFieldOrder[1] = sortField2SortOrder;
		}
		if (sortField3 != null && !sortField3.equals(""))
		{
			this.sortFieldGetterMethods[2] = getMethodName(sortField3);
			if (this.isMap) this.sortFieldMethodArgs[2]=sortField3; // Map objects use method "get" and pass the field name as an arg
			this.sortFieldOrder[2] = sortField3SortOrder;
		}
	}

	/**
	 * Checks if the Class object currently set and associated with this ObjectComparator
	 * implements interface Map
	 *
	 */
	private void checkIsMap() {

		/*
		if (this.sortObjectClass.isInstance( aMapObject ) ) {
			this.isMap = true;
		}
		*/
		/*
		 * The method Class.isInstance(Object) above doesn't seem to work.  Even when I created
		 * an explicit instance of the object to sort (like a 'Hashtable'), it never
		 * detected the correct object type.  Instead, I am trying to get a hard instance
		 * of the object so I can check instanceof, which *does* seem to work.  If I can't
		 * create an instance of the object using the empty constructor, then I'm assuming 
		 * it's not a Map!
		 */ 
		try {
			Object o = this.sortObjectClass.newInstance();
			if (o instanceof Map) {
				this.isMap = true;				
			}
		}
		catch (InstantiationException e)
		{
			// Do nothing, just assume it's not a map
			/*
			throw new ObjectComparatorException(
					"could not check for map '"
						+ " (map=" + this.isMap + ") findmap: " + findmap.toString() 
						+ "'. Error is: "
						+ e); */
		}
		catch (IllegalAccessException e)
		{
			// Do nothing, just assume it's not a map
		}
			
	}
	
	
	/**
	 * The implementation of the Comparator interface's compare method
	 * 
	 * @param obj1 The first object to compare 
	 * @param obj2 The second object to compare
	 * @return int
	 * @see java.util.Comparator#compare(Object, Object)
	 **/
	public int compare(Object obj1, Object obj2)
	{
		int returnVal = 0;
		try
		{
			Object obj1FieldValue = null;
			Object obj2FieldValue = null;

			int length = sortFieldGetterMethods.length;
			Method getterMethod = null;
			String[] methodArg = null;

			for (int i = 0; i < length; i++)
			{
				getterMethod = sortFieldGetterMethods[i];
				methodArg = new String[] { sortFieldMethodArgs[i] };

				if (getterMethod != null)
				{
					
					if (this.isMap) {
						// If we're a Map, we need to invoke get() method with the key as an argument
						obj1FieldValue = getterMethod.invoke(obj1, methodArg);
						obj2FieldValue = getterMethod.invoke(obj2, methodArg);
					} else {
						// If we're not a Map, we need to invoke the getter method
						obj1FieldValue = getterMethod.invoke(obj1, null);
						obj2FieldValue = getterMethod.invoke(obj2, null);
					}
					
					if(obj1FieldValue == null){
					    if (obj2FieldValue instanceof String )
					        obj1FieldValue = new String();
					    else if (obj2FieldValue instanceof Integer )
					        obj1FieldValue = new Integer(0);
					    else if (obj2FieldValue instanceof Long)
					        obj1FieldValue = new Long(0);
					    else if (obj2FieldValue  instanceof Float)
					        obj1FieldValue = new Float(0);
					    else if (obj2FieldValue instanceof Double)
					        obj1FieldValue = new Double(0);
					    else if (obj2FieldValue instanceof Date)
					        obj1FieldValue = new Date(0);
					    else obj1FieldValue = new Object();
					}
					
					if (obj1FieldValue instanceof String )
					{
						if ( obj2FieldValue == null )
						{
							obj2FieldValue = new String();
						}						
						returnVal =
							obj1FieldValue.toString().toUpperCase().compareTo(
								obj2FieldValue.toString().toUpperCase());
					}
					else if (obj1FieldValue instanceof Integer)
					{
						if ( obj2FieldValue == null )
						{
							obj2FieldValue = new Integer(0);
						}						
						Integer obj1Integer = (Integer) obj1FieldValue;
						Integer obj2Integer = (Integer) obj2FieldValue;
						returnVal = obj1Integer.compareTo(obj2Integer);
					}
					else if (obj1FieldValue instanceof Long)
					{
						if ( obj2FieldValue == null )
						{
							obj2FieldValue = new Long(0);
						}						
						Long obj1Long = (Long) obj1FieldValue;
						Long obj2Long = (Long) obj2FieldValue;
						returnVal = obj1Long.compareTo(obj2Long);
					}
					else if (obj1FieldValue instanceof Float)
					{
						if ( obj2FieldValue == null )
						{
							obj2FieldValue = new Float(0);
						}						
						Float obj1Float = (Float) obj1FieldValue;
						Float obj2Float = (Float) obj2FieldValue;
						returnVal = obj1Float.compareTo(obj2Float);
					}
					else if (obj1FieldValue instanceof Double)
					{
						if ( obj2FieldValue == null )
						{
							obj2FieldValue = new Double(0);
						}						
						Double obj1Double = (Double) obj1FieldValue;
						Double obj2Double = (Double) obj2FieldValue;
						returnVal = obj1Double.compareTo(obj2Double);
					}
					else if (obj1FieldValue instanceof Date)
					{
						if ( obj2FieldValue == null )
						{
							obj2FieldValue = new Date(0);
						}						
						
						Date obj1Date = (Date) obj1FieldValue;
						Date obj2Date = (Date) obj2FieldValue;
						returnVal = obj1Date.compareTo(obj2Date);
					}
					else
					{
						if ( obj2FieldValue == null )
						{
							obj2FieldValue = new Object();
						}									
						returnVal =
							obj1FieldValue.toString().compareTo(
								obj2FieldValue.toString());
					}
					if (returnVal != 0)
					{
						if (sortFieldOrder[i] == SORT_DESCENDING)
						{
							returnVal = returnVal * -1;
						}
						break;
					}
				}
			}
		}
		catch (Exception e)
		{
			//Can't throw an exception since the inherited compare method doesn't define it
			// That means any exception when comparing the objects goes into a black hole, and
			// you won't be able to know why your compare didn't work.
			returnVal = 0;
		}
		return returnVal;
	}

	/**
	 * Used to retrieve the 'getter' method, as a Method object, for the passed in field name
	 * 
	 * @param fieldName The field name
	 * @return The getter method
	 * @throws ObjectComparatorException
	 **/
	private Method getMethodName(String fieldName)
		throws ObjectComparatorException
	{
		Method theMethod = null;
		String methodName = null;
		Class[] parameterTypes = null;
		String firstChar = "";
		if (fieldName != null && !fieldName.equals(""))
		{
			if (this.isMap) {
				// If we're a Map, we'll always check for method "get()"
				methodName="get";
				// Object o = new Object();
				Object o = new Object(); 
				parameterTypes = new Class[] { o.getClass() };
				
			} else {
				// If we're not a Map, check for bean-style get method (i.e. "getMyProperty()")
				firstChar = fieldName.substring(0, 1);
				if (fieldName.length() >= 2)
				{
					methodName =
						"get" + firstChar.toUpperCase() + fieldName.substring(1);
				}
				else
				{
					methodName = "get" + firstChar.toUpperCase();
				}
			
			}
		}
		try
		{
			theMethod = sortObjectClass.getMethod(methodName, parameterTypes);
		}
		catch (NoSuchMethodException e)
		{
			throw new ObjectComparatorException(
				"No getter method matches the field name passed in '"
					+ fieldName + " (map=" + this.isMap + ") "
					+ "'. Error is: "
					+ e);
		}
		return theMethod;
	}
}
